﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Xml.Linq;
using Phoenix.Xna.Framework.Properties;
using Phoenix.Xna.Framework.Reflection;
using Phoenix.Xna.Framework.Serialization.Markup.Fields;
using Phoenix.Xna.Framework.Serialization.Markup.Objects;
using Phoenix.Xna.Framework.Xml;

namespace Phoenix.Xna.Framework.Serialization.Markup.Properties
{
    internal class CollectionMarkupProperty : MarkupPropertyBase<object>
    {
        protected object _value;

        /// <summary>
        /// Gets all properties that are not set to default values.
        /// </summary>
        public List<IMarkupProperty> Properties { get; protected set; }

        /// <summary>
        /// Gets all fields that are not set to default values.
        /// </summary>
        public List<IMarkupField> Fields { get; protected set; }

        public CollectionMarkupProperty()
        {
            Properties = new List<IMarkupProperty>();
            Fields = new List<IMarkupField>();
        }

        private void AddAsProperty(PropertyInfo property, XElement propertyValue)
        {
            IMarkupProperty markupPropery = Serializer.MarkupSelector.GetMarkupPropertyFor(property);
            Properties.Add(markupPropery);
            markupPropery.Name = property.Name;
            markupPropery.Deserialize(propertyValue);
        }

        private void AddAsField(FieldInfo field, XElement fieldValue)
        {
            IMarkupField markupField = Serializer.MarkupSelector.GetMarkupFieldFor(field);
            Fields.Add(markupField);
            markupField.Name = field.Name;
            markupField.Deserialize(fieldValue);
        }

        #region IMarkupElement Members

        /// <summary>
        /// Serialize the element to XML.
        /// </summary>
        /// <returns>The element markup serialzed as an XElement.</returns>
        public override XElement Serialize()
        {
            XElement element = new XElement(NamespaceCache.GetNamespaceFor(ObjectType) + ObjectType.Name.Replace('`', '-'));
            element.SetAttribute(AttributeIDs.PropertyIDAttribute, Name);

            foreach (IMarkupField markupField in Fields)
            {
                element.Add(markupField.Serialize());
            }

            foreach (IMarkupProperty markupProperty in Properties)
            {
                element.Add(markupProperty.Serialize());
            }

            return element;
        }

        /// <summary>
        /// Deserialize the element from an XElement.
        /// </summary>
        /// <param name="element">The XElement to deserialize this element from.</param>
        public override void Deserialize(XElement element)
        {
            Name = element.ReadAttribute<string>(AttributeIDs.PropertyIDAttribute);
            ObjectType = NamespaceCache.GetType(element.Name.LocalName.Replace("-", "`"), element.Name.Namespace);

            foreach (XElement node in element.Nodes())
            {
                if (node.ReadAttribute<bool>(AttributeIDs.IsFieldAttribute))
                {
                    string fieldID = node.ReadAttribute<string>(AttributeIDs.FieldIDAttribute);

                    if (!string.IsNullOrEmpty(fieldID))
                    {
                        FieldInfo field = ObjectType.GetField(fieldID);

                        if (field != null)
                        {
                            AddAsField(field, node);
                        }
                    }
                }
                else
                {
                    string propertyID = node.ReadAttribute<string>(AttributeIDs.PropertyIDAttribute);

                    if (!string.IsNullOrEmpty(propertyID))
                    {
                        PropertyInfo property = ObjectType.GetProperty(propertyID);

                        if (property != null)
                        {
                            AddAsProperty(property, node);
                        }
                    }
                }
            }
        }

        #endregion

        #region IMarkupProperty Members

        /// <summary>
        /// Extract the markup information from the PropertyInfo for this property.
        /// </summary>
        /// <param name="field">The PropertyInfo for this property.</param>
        /// <param name="value">The value of the property.</param>
        public override void ExtractMarkup(PropertyInfo property, object value)
        {
            ObjectType = property.PropertyType;
            Name = property.Name;

            if (value != null && (property.GetFirstAttributeOfType<SerializableIDictionaryAttribute>(false) != null || property.GetFirstAttributeOfType<SerializableIListAttribute>(false) != null))
            {       
                foreach (FieldInfo f in ObjectType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
                {
                    IMarkupField markupField = Serializer.MarkupSelector.GetMarkupFieldFor(f);

                    if (markupField != null)
                    {
                        markupField.ExtractMarkup(f, f.GetValue(value));

                        if (markupField.ShouldPersist)
                        {
                            Fields.Add(markupField);
                        }
                    }
                }

                foreach (PropertyInfo p in ObjectType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
                {
                    IMarkupProperty markupProperty = Serializer.MarkupSelector.GetMarkupPropertyFor(p);

                    if (markupProperty != null)
                    {
                        markupProperty.ExtractMarkup(p, p.GetValue(value, null));

                        if (markupProperty.ShouldPersist)
                        {
                            Properties.Add(markupProperty);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Sets the value of MarkupProperty to the instance's property value.
        /// </summary>
        /// <param name="instance">The instance to set the property on.</param>
        public override void SetValue(object instance)
        {
            PropertyInfo property = instance.GetType().GetProperty(Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
            _value = property.GetValue(instance, null);

            if (_value == null)
            {
                _value = Activator.CreateInstance(property.PropertyType);
                property.SetValue(instance, _value, null);
            }            

            foreach (IMarkupField f in Fields)
            {
                f.SetValue(_value);
            }

            foreach (IMarkupProperty p in Properties)
            {
                p.SetValue(_value);
            }
        }

        #endregion
    }
}
